﻿// ----------------------------------------------------------------------------------
// Sample Code from Fernando Machado Píriz's blog: http://fernandomachadopiriz.com
//
// <copyright company="Fernando Machado Píriz"  file="SettingsManager.cs">
// Copyright (c) Fernando Machado Píriz. Distributed under FSF's GPL v2.</copyright>
// ----------------------------------------------------------------------------------
// This code and information are provided "as is" without warranty of any kind,
// either expressed of implied, including but not limited to the implied warranties
// of merchantability and/or fitness for a particular purpose.
// ----------------------------------------------------------------------------------
namespace GivingAPresentation.Commons
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Xml;

    /// <summary>
    /// Loads and stores settings as named values using isolated storage by default. This way there is no need to change settings file
    /// permissions as with standard application settingsManager files.
    /// </summary>
    public class SettingsManager
    {
        /// <summary>
        /// The name of the file where settings are stored.
        /// </summary>
        private const string SettingsFileName = "GivingAPresentation.xlm";

        /// <summary>
        /// The backend field for the <code>Instance</code> property.
        /// </summary>
        private static SettingsManager instance;

        /// <summary>
        /// An internal lock to avoid concurrent access to internal state.
        /// </summary>
        private static object thisLock = new object();

        /// <summary>
        /// An Xml document containing the current settings or null if settings has not changed yet.
        /// </summary>
        private XmlDocument currentSettings;

        /// <summary>
        /// A Xml document containing the original settings or null if settings does not exist.
        /// </summary>
        private XmlDocument originalSettings;

        /// <summary>
        /// A value indicating if settings has been loaded or not. Settings are loaded automatically the first time they are needed.
        /// </summary>
        private bool loaded = false;

        /// <summary>
        /// Prevents a default instance of the SettingsManager class from being created. Only this class can create its own instance using
        /// the Singleton pattern.
        /// </summary>
        private SettingsManager()
        {
        }

        /// <summary>
        /// Gets the class instance exposed using the Singleton pattern.
        /// </summary>
        public static SettingsManager Instance
        {
            get
            {
                lock (thisLock)
                {
                    if (instance == null)
                    {
                        instance = new SettingsManager();
                    }

                    return instance;
                }
            }
        }

        /// <summary>
        /// Retrieves a bool value previously stored with the given name or the given default value.
        /// </summary>
        /// <param name="name">The name of the value previosly stored.</param>
        /// <param name="defaultValue">The default value if previously stored value is found.</param>
        /// <returns>A bool value.</returns>
        public bool GetValue(string name, bool defaultValue)
        {
            if (!this.loaded)
            {
                this.Load();
            }

            return this.ReadBoolean(name, defaultValue);
        }

        /// <summary>
        /// Retrieves an int value previously stored with the given name or the given default value.
        /// </summary>
        /// <param name="name">The name of the value previosly stored.</param>
        /// <param name="defaultValue">The default value if previously stored value is found.</param>
        /// <returns>An int value.</returns>
        public int GetValue(string name, int defaultValue)
        {
            if (!this.loaded)
            {
                this.Load();
            }

            return this.ReadInt32(name, defaultValue);
        }

        /// <summary>
        /// Retrieves a string value previously stored with the given name or the given default value.
        /// </summary>
        /// <param name="name">The name of the value previosly stored.</param>
        /// <param name="defaultValue">The default value if previously stored value is found.</param>
        /// <returns>A string value.</returns>
        public string GetValue(string name, string defaultValue)
        {
            if (!this.loaded)
            {
                this.Load();
            }

            return this.ReadString(name, defaultValue);
        }

        /// <summary>
        /// Stores a bool value with the given name.
        /// </summary>
        /// <param name="name">The name of the value to store.</param>
        /// <param name="value">The bool value to store.</param>
        public void SetValue(string name, bool value)
        {
            this.WriteString(name, value.ToString());
        }

        /// <summary>
        /// Stores an int value with the given name.
        /// </summary>
        /// <param name="name">The name of the value to store.</param>
        /// <param name="value">The int value to store.</param>
        public void SetValue(string name, int value)
        {
            this.WriteString(name, value.ToString(CultureInfo.InvariantCulture));
        }

        /// <summary>
        /// Stores a string value with the given name.
        /// </summary>
        /// <param name="name">The name of the value to store.</param>
        /// <param name="value">The string value to store.</param>
        public void SetValue(string name, string value)
        {
            this.WriteString(name, value);
        }

        /// <summary>
        /// Load settings from stream provided by StreamReaderNeeded handler.
        /// </summary>
        public void Load()
        {
            this.loaded = true;

            FileStream stream = null;
            try
            {
                stream = GetIsolatedStorageFileStreamToRead();

                // The stream local variable can be null if this is the first time the application in run. In such case currentSettings and
                // originalSettings fields are also null.
                if (stream != null)
                {
                    using (StreamReader streamReader = new StreamReader(stream))
                    {
                        stream = null;
                        this.currentSettings = new XmlDocument();
                        this.currentSettings.Load(streamReader);
                        this.originalSettings = new XmlDocument();
                        this.originalSettings.LoadXml(this.currentSettings.OuterXml);
                    }
                }
            }
            finally
            {
                if (stream != null)
                {
                    stream.Dispose();
                }
            }
        }

        /// <summary>
        /// Save settings to stream provided by StreamWriterNeeded handler.
        /// </summary>
        public void Save()
        {
            // Do nothing if settings do not exists.
            if (this.currentSettings == null)
            {
                return;
            }

            // Do nothing if settings have not changed.
            if ((this.originalSettings != null) && (this.currentSettings.OuterXml == this.originalSettings.OuterXml))
            {
                return;
            }

            FileStream stream = null;
            try
            {
                stream = GetIsolatedStorageFileStreamToWrite();
                using (StreamWriter streamWriter = new StreamWriter(stream))
                {
                    stream = null;
                    this.currentSettings.Save(streamWriter);
                    if (this.originalSettings == null)
                    {
                        this.originalSettings = new XmlDocument();
                    }

                    this.originalSettings.LoadXml(this.currentSettings.OuterXml);
                }
            }
            finally
            {
                if (stream != null)
                {
                    stream.Dispose();
                }
            }
        }

        /// <summary>
        /// A default implementation handler of StreamReaderNeeded using a isolated storage file stream.
        /// </summary>
        /// <returns>A stream reader to load settingsManager from.</returns>
        private static IsolatedStorageFileStream GetIsolatedStorageFileStreamToRead()
        {
            IsolatedStorageFile isf;
            string[] fileNames;

            isf = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);
            fileNames = isf.GetFileNames(SettingsFileName);

            foreach (string fileName in fileNames)
            {
                if (fileName.Equals(SettingsFileName))
                {
                    return new IsolatedStorageFileStream(SettingsFileName, FileMode.Open, isf);
                }
            }

            return null;
        }

        /// <summary>
        /// A default implementation handler of StreamWriterNeeded using a isolated storage file stream.
        /// </summary>
        /// <returns>A StreamWriter to store settingsManager to.</returns>
        private static IsolatedStorageFileStream GetIsolatedStorageFileStreamToWrite()
        {
            IsolatedStorageFile isf;

            isf = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);

            return new IsolatedStorageFileStream(SettingsFileName, FileMode.Create, isf);
        }

        /// <summary>
        /// Retrieves a string value previously stored with the given name or an empty string.
        /// </summary>
        /// <param name="name">The name of the value previosly stored.</param>
        /// <returns>A string value.</returns>
        private string ReadString(string name)
        {
            return this.ReadString(name, String.Empty);
        }

        /// <summary>
        /// Retrieves a string value previously stored with the given name or the given default value.
        /// </summary>
        /// <param name="name">The name of the value previosly stored.</param>
        /// <param name="defaultValue">The default value if previously stored value is found.</param>
        /// <returns>A string value.</returns>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Catching the exception and returning a default value is okay in this case")]
        private string ReadString(string name, string defaultValue)
        {
            try
            {
                if (this.currentSettings == null)
                {
                    return String.Empty;
                }

                XmlNode node = this.currentSettings.SelectSingleNode((@"/settingsManager/" + name));
                if (node == null)
                {
                    return defaultValue;
                }

                return node.FirstChild.Value;
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Retrieves an int value previously stored with the given name or the given default value.
        /// </summary>
        /// <param name="name">The name of the value previosly stored.</param>
        /// <param name="defaultValue">The default value if previously stored value is found.</param>
        /// <returns>An int value.</returns>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Catching the exception and returning a default value is okay in this case")]
        private int ReadInt32(string name, int defaultValue)
        {
            string valuestring = this.ReadString(name);
            if (valuestring.Length <= 0)
            {
                return defaultValue;
            }

            try
            {
                int value = Convert.ToInt32(valuestring, CultureInfo.InvariantCulture);
                return value;
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Retrieves a boolean value previously stored with the given name or the given default value.
        /// </summary>
        /// <param name="name">The name of the value previosly stored.</param>
        /// <param name="defaultValue">The default value if previously stored value is found.</param>
        /// <returns>A boolean value.</returns>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Catching the exception and returning a default value is okay in this case")]
        private bool ReadBoolean(string name, bool defaultValue)
        {
            string value = this.ReadString(name);
            if (value.Length <= 0)
            {
                return defaultValue;
            }

            try
            {
                return Boolean.Parse(value);
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Stores a string value with the given name.
        /// </summary>
        /// <param name="name">The name of the value to store.</param>
        /// <param name="value">The string value to store.</param>
        private void WriteString(string name, string value)
        {
            if (this.currentSettings == null)
            {
                this.currentSettings = new XmlDocument();
                XmlNode rootNode = this.currentSettings.CreateElement(@"settingsManager");
                this.currentSettings.AppendChild(rootNode);
            }

            XmlNode node = this.currentSettings.SelectSingleNode((@"/settingsManager/" + name));
            if (node == null)
            {
                node = this.currentSettings.CreateElement(name);
                XmlNode configurationRootNode = this.currentSettings.SelectSingleNode(@"/settingsManager");
                configurationRootNode.AppendChild(node);
            }

            node.InnerText = value;
        }
    }
}
